package com.mytx.sso.controller;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mytx.common.annotation.SerializedField;
import com.mytx.common.constant.Constant;
import com.mytx.common.pojo.CommonResult;
import com.mytx.common.pojo.RedisLock;
import com.mytx.common.utils.DateUtils;
import com.mytx.common.utils.IDUtils;
import com.mytx.mapper.CouponMapper;
import com.mytx.mapper.UserMapper;
import com.mytx.pojo.Coupon;
import com.mytx.pojo.Topic;
import com.mytx.pojo.User;
import com.mytx.sso.service.ConsumerSearchService;
import com.mytx.sso.utils.PayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;


@SuppressWarnings({"SpringJavaInjectionPointsAutowiringInspection", "Duplicates"})
@RestController
public class CouponController {
    private static Logger logger = LoggerFactory.getLogger(CouponController.class);

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    UserMapper userMapper;
    @Autowired
    CouponMapper couponMapper;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private ConsumerSearchService searchService;

    /**
     * 发送金币红包，发红包的时候，对发送者的余额做较少操作
     *
     * @param
     * @return
     */
    @PostMapping("/sendSingleCoupon")
    public CommonResult sendSingleCoupon(@RequestBody JSONObject body) {

        Coupon coinCoupon = body.getJSONObject("coupon").toJavaObject(Coupon.class);

        String uId = request.getHeader("uId");
        //  CommonResult validatePasswordResult = validatePassword(body, donation, uId);
        User user = getUser(uId);
        BigDecimal amount = coinCoupon.getAmount();
        String payPassword = body.getString("payPassword");
        RedisLock lock = new RedisLock(redisTemplate, Constant.USER_SESSION_LOCK + uId);

        try {
            if (!lock.lock()) {
                logger.error("sendSingleCoupon：发送红包，获取锁失败了,uId:{}", uId);
                return CommonResult.build(Constant.GET_LOCK_FAIL, "发送红包，获取锁失败了");
            }

            CommonResult commonResult = PayUtils.payPrepare(payPassword, amount, user);
            if (commonResult != null) {
                return commonResult;
            }
            String couponId = IDUtils.genOrderItemId();
            coinCoupon.setId(couponId);
            coinCoupon.setuId(uId);
            coinCoupon.setStatus(Constant.COUPON_STATUS_RECEIVABLE);
            Date currentDate = new Date();
            Date expiryDate = DateUtils.addDay(currentDate, +1);//获取当前日期后一天的时间（过期时间）
            coinCoupon.setExpiryDate(expiryDate);
            coinCoupon.setCreated(currentDate);
            couponMapper.insertSelective(coinCoupon);
            BigDecimal mysqlBalance = userMapper.reduceBalance(uId, amount);
            redisTemplate.opsForHash().put(Constant.USER_SESSION + uId, "balance", mysqlBalance);//以mysql 余额为准

            String formatDate = DateUtils.formatDate(expiryDate, "yyyy-MM-dd-HH");
            redisTemplate.opsForSet().add(Constant.COUPON_SET + formatDate, coinCoupon.getId());
            redisTemplate.opsForValue().set(Constant.COUPON_RECORD + coinCoupon.getId(), coinCoupon, 26, TimeUnit.HOURS);
            logger.info("sendSingleCoupon:余额减少 uId:{},amount:{},balance:{},couponId:{}", uId, amount, mysqlBalance, couponId);

            JSONObject response = new JSONObject();
            response.put("expiryDate", coinCoupon.getExpiryDate());
            response.put("balance", mysqlBalance);
            response.put("couponId", coinCoupon.getId());
            return CommonResult.ok(response);
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("sendSingleCoupon：发红包出错：未知错误+message:{}", e.getMessage());
            return CommonResult.build(Constant.FUND_COUPON_EOOR, "未知错误");
        } finally {
            lock.unlock();
        }

    }

    /**
     * 发一对多红包 就是群红包
     *
     * @param body
     * @return
     */
    @Transactional
    @PostMapping("/sendMultipleCoupon")
    // @SerializedField(encrypt = false)
    public CommonResult sendMultipleCoupon(@RequestBody JSONObject body) {
        String uId = request.getHeader("uId");
        Coupon multipleCoinCoupon = body.getJSONObject("coupon").toJavaObject(Coupon.class);
        User user = getUser(uId);
        BigDecimal amount = multipleCoinCoupon.getAmount();
        String payPassword = body.getString("payPassword");
        RedisLock lock = new RedisLock(redisTemplate, Constant.USER_SESSION_LOCK + uId);

        try {
            if (!lock.lock()) {
                logger.error("发送红包，获取锁失败了,uId:{}", uId);
                return CommonResult.build(Constant.GET_LOCK_FAIL, "发送红包，获取锁失败了");
            }

            CommonResult commonResult = PayUtils.payPrepare(payPassword, amount, user);
            if (commonResult != null) {
                return commonResult;
            }
            String couponId = IDUtils.genOrderItemId();
            multipleCoinCoupon.setId(couponId);
            multipleCoinCoupon.setuId(uId);
            multipleCoinCoupon.setStatus(Constant.COUPON_STATUS_RECEIVABLE);
            multipleCoinCoupon.setBalance(multipleCoinCoupon.getAmount());//发一对多红包的时候，红包余额等于红包金额
            multipleCoinCoupon.setReceivedCount(0);
            Date currentDate = multipleCoinCoupon.getCreated();
            Date expiryDate = DateUtils.addDay(currentDate, +1);//获取当前日期后一天的时间
            multipleCoinCoupon.setExpiryDate(expiryDate);
            // multipleCoinCoupon.setCreated(currentDate);
            if (currentDate == null) {
                currentDate = new Date();
            }
            // multipleCoinCoupon.setCreated(currentDate);
            couponMapper.insertSelective(multipleCoinCoupon);
            BigDecimal mysqlBalance = userMapper.reduceBalance(uId, amount);
            String formatDate = DateUtils.formatDate(expiryDate, "yyyy-MM-dd-HH");
            redisTemplate.opsForSet().add(Constant.COUPON_SET + formatDate, multipleCoinCoupon.getId());
            redisTemplate.opsForValue().set(Constant.COUPON_RECORD + multipleCoinCoupon.getId(), multipleCoinCoupon, 26, TimeUnit.HOURS);
            //如果直播间id 不为空，放一份进直播间，方便进入直播间获取数据时候能同时返回 红包信息
            String liveRoomId = multipleCoinCoupon.getLiveRoomId();
            String topicId = multipleCoinCoupon.getTopicId();
            if (liveRoomId != null) {
                User newUser = new User();
                newUser.setuId(user.getuId());
                newUser.setAvatar(user.getAvatar());
                newUser.setUsername(user.getUsername());
                multipleCoinCoupon.setTargetUser(newUser);
                redisTemplate.opsForHash().put(Constant.LIVE_ROOM + liveRoomId, "coupon", multipleCoinCoupon);

            } else if (topicId != null) {//如果主题id 不为空，说明这个红包是依附主题的红包，需要更新进ES
                Topic updateTopic = new Topic();
                updateTopic.setId(topicId);
                updateTopic.setCoupon(multipleCoinCoupon);
                searchService.update(updateTopic);
                JSONObject info = new JSONObject();
                info.put("topicId", topicId);
                redisTemplate.convertAndSend(Constant.NOTIFY_FOUND_COUPON, info);
            }
            redisTemplate.opsForHash().put(Constant.USER_SESSION + uId, "balance", mysqlBalance);//以mysql 余额为准
            logger.info("sendMultipleCoupon:余额减少 uId:{},amount:{},balance:{},couponId:{}", uId, amount, mysqlBalance, couponId);//日志放在最后，避免前面某个地方出差，事物触发，但是日志却记录了用户余额变动的情况
            JSONObject response = new JSONObject();

            response.put("balance", mysqlBalance);
            response.put("expiryDate", multipleCoinCoupon.getExpiryDate());
            response.put("couponId", multipleCoinCoupon.getId());
            return CommonResult.ok(response);
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("发红包出错：未知错误+message:{}", e.getMessage());
            return CommonResult.build(Constant.FUND_COUPON_EOOR, "未知错误");
        } finally {
            lock.unlock();
        }
    }

    /**
     * 接收一对一红包
     *
     * @param singleCoupon
     * @return
     */
    @PostMapping("/receiveSingleCoupon")
    public CommonResult receiveSingleCoupon(@RequestBody Coupon singleCoupon) {
        String uId = request.getHeader("uId");
        String couponId = singleCoupon.getId();
        RedisLock lock = new RedisLock(redisTemplate, Constant.COUPON_LOCK + couponId);
        try {
            if (!lock.lock()) {
                return CommonResult.build(Constant.GET_LOCK_FAIL, "领取红包，获取锁失败了");
            }
            Object couponObject = redisTemplate.opsForValue().get(Constant.COUPON_RECORD + couponId);

            if (couponObject == null) {
                return CommonResult.build(Constant.FUND_COUPON_STATUS_EXPIRED, "红包已经过期");
            }
            singleCoupon = (Coupon) couponObject;
            if (!StringUtils.equals(singleCoupon.gettId(), uId)) {
                return CommonResult.build(Constant.FUND_COUPON_TARGET_USER_MISMATCHING, "红包接收人不匹配");
            }

            //更新到mysql
            BigDecimal mysqlBalance = userMapper.incrementBalance(uId, singleCoupon.getAmount());
            //更新缓存
            redisTemplate.opsForHash().put(Constant.USER_SESSION + uId, Constant.BALANCE, mysqlBalance);

            //删除redis 中的缓存记录
            Boolean delete = redisTemplate.delete(Constant.COUPON_RECORD + couponId);
            if (!delete) {//todo 也许这儿应该抛出异常，让整个操作回滚
                logger.error("接受红包后，清除红包缓存出错：uId:{},", uId, singleCoupon);
            }
            Date date = singleCoupon.getExpiryDate();
            String formatDate = DateUtils.formatDate(date, "yyyy-MM-dd-HH");
            String key = Constant.COUPON_SET + formatDate;
            Long remove = redisTemplate.opsForSet().remove(key, couponId);
            if (remove != 1) {
                logger.error("接受红包后，清除红包set出错：" + uId, singleCoupon);//如果这儿没清除掉，在红包到期清理的定时任务中，会把红包金额又返还给发送者，这样就出大问题了，必须手动清理掉
            }
            singleCoupon.setStatus(Constant.COUPON_STATUS_RECEIVED);//设置红包状态为已领取
            couponMapper.updateByPrimaryKeySelective(singleCoupon);
            logger.info("receiveSingleCoupon:uId:{},amount:{},balance:{},couponId:{}", uId, singleCoupon.getAmount(), mysqlBalance, couponId);
            return CommonResult.ok(mysqlBalance);
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("receiveSingleCoupon：接收红包出错，uId:{}", uId, singleCoupon, e.getMessage());
            return CommonResult.build(Constant.FUND_COUPON_EOOR, "接收红包出错，未知错误");
        } finally {
            lock.unlock();
        }
    }


    /**
     * 领取一对多红包
     *
     * @param multipleCoupon 父红包：一方红包
     *                       子红包：多方红包
     *                       领取一对多红包的时候，当红包没领完，目前没有对父红包做持久化操作，仅更新进了redis,红包是否领完，是依赖redis中父红包余额进行判断的，当父红包被完全领完了，才更新进 mysql,
     * @return
     */
    @Transactional
    @PostMapping("/receiveMultipleCoupon")
    public CommonResult receiveMultipleCoupon(@RequestBody Coupon multipleCoupon) {
        boolean isEmpty = false;
        String uId = request.getHeader("uId");
        // logger.info("receiveMultipleCoupon：uid:{} 进入抢红包，红包id:{}", uId, multipleCoupon.getId());

        String parentCouponId = multipleCoupon.getId();
        RedisLock lock = new RedisLock(redisTemplate, Constant.COUPON_LOCK + parentCouponId);
        try {
            if (!lock.lock()) {
                return CommonResult.build(Constant.GET_LOCK_FAIL, "领取红包，获取锁失败了");
            }

            Object couponObject = redisTemplate.opsForValue().get(Constant.COUPON_RECORD + parentCouponId);
            if (couponObject == null) {
                return CommonResult.build(Constant.FUND_COUPON_STATUS_EXPIRED, "红包已经过期");
            }
            Boolean member = redisTemplate.opsForSet().isMember(Constant.COUPON_RECEIVED_RECORD + parentCouponId, uId);
            if (member) {
                return CommonResult.build(Constant.RECEIVE_COUPON_ERROR_RECEIVED, "你已经领取过了");
            }
            Coupon parentCoupon = (Coupon) couponObject;
            BigDecimal amount = parentCoupon.getAmount();
            BigDecimal balance = parentCoupon.getBalance();
            Integer totalCount = parentCoupon.getTotalCount();
            Integer receivedCount = parentCoupon.getReceivedCount();
            if (balance.compareTo(BigDecimal.ZERO) == 0 || receivedCount == totalCount || parentCoupon.getStatus() == Constant.COUPON_STATUS_RECEIVED) {
                return CommonResult.build(Constant.RECEIVE_COUPON_ERROR_REMAIN_ZERO, "领完了");
            }
            BigDecimal receiveAmount;

            if (parentCoupon.getType() == Constant.COUPON_TYPE_MULTIPLE_AVERAGE) {
                receiveAmount = amount.divide(new BigDecimal(totalCount)).setScale(2, BigDecimal.ROUND_DOWN);

            } else {
                //剩余可领的平均金额
                BigDecimal avgReceiveAmount = balance.divide(new BigDecimal(totalCount - receivedCount), 2, BigDecimal.ROUND_DOWN);
                Random random = new Random();
                receiveAmount = avgReceiveAmount.multiply(new BigDecimal(2)).multiply(new BigDecimal(random.nextDouble())).setScale(2, BigDecimal.ROUND_DOWN);//乘以2是为了生成的领取领取金额最大值为平均金额的2倍
                if (receiveAmount.compareTo(new BigDecimal(0.01)) < 0) {
                    receiveAmount = new BigDecimal(0.01);
                }
            }
            receivedCount++;
            //判断是否是最后一个红包
            if (receivedCount == totalCount) {
                receiveAmount = balance;
                balance = BigDecimal.ZERO;
                isEmpty = true;

            } else {
                balance = balance.subtract(receiveAmount);
            }
            parentCoupon.setBalance(balance);
            parentCoupon.setReceivedCount(receivedCount);
            //生成子红包
            Coupon childCoupon = new Coupon();
            String childCouponId = IDUtils.genOrderItemId();
            childCoupon.setId(childCouponId);
            childCoupon.setParentId(parentCoupon.getId());//设置自红包的父红包id
            childCoupon.setuId(parentCoupon.getuId());//设置发送方用户id
            childCoupon.settId(uId);//设置接收方用户id
            childCoupon.setAmount(receiveAmount);
            if (parentCoupon.getType() == Constant.COUPON_TYPE_MULTIPLE_AVERAGE) {
                childCoupon.setType(Constant.COUPON_TYPE_CHILD_AVERAGE);
            } else {
                childCoupon.setType(Constant.COUPON_TYPE_CHILD_RANDOM);
            }
            Date currentDate = new Date();
            childCoupon.setCreated(currentDate);
            //子红包持久化
            couponMapper.insertSelective(childCoupon);
            //更新用户余额到mysql
            BigDecimal mysqlBalance = userMapper.incrementBalance(uId, receiveAmount);
            //如果父红包已经领取完，把父红包更新进mysql,设置领取完
            if (receivedCount == totalCount) {
                parentCoupon.setStatus(Constant.COUPON_STATUS_REMAIN_ZERO);
                parentCoupon.setUpdated(currentDate);
                couponMapper.updateByPrimaryKeySelective(parentCoupon);

                //清除直播间中的红包缓存信息
                if (parentCoupon.getLiveRoomId() != null) {
                    redisTemplate.opsForHash().delete(Constant.LIVE_ROOM + parentCoupon.getLiveRoomId(), "coupon");
                } else if (parentCoupon.getTopicId() != null) {
                    searchService.deleteCoupon(parentCoupon.getTopicId());
                }
                //清除定时任务所需的SET缓存
                Date date = parentCoupon.getExpiryDate();
                String formatDate = DateUtils.formatDate(date, "yyyy-MM-dd-HH");
                String key = Constant.COUPON_SET + formatDate;
                redisTemplate.opsForSet().remove(key, parentCouponId);
                //清除红包缓存
                redisTemplate.delete(Constant.COUPON_RECORD + parentCouponId);
                //清除领取记录
                redisTemplate.delete(Constant.COUPON_RECEIVED_RECORD + parentCouponId);
            } else {
                redisTemplate.opsForSet().add(Constant.COUPON_RECEIVED_RECORD + parentCouponId, uId);
                //把更新了红包余额和领取个数的红包  再放进缓存里面
                redisTemplate.opsForValue().set(Constant.COUPON_RECORD + parentCoupon.getId(), parentCoupon);
            }
            //更新用户缓存
            redisTemplate.opsForHash().put(Constant.USER_SESSION + uId, Constant.BALANCE, mysqlBalance);
            logger.info("receiveMultipleCoupon:用户余额增加 uId:{},amount:{},balance:{},parentCouponId:{},childCouponId:{}", uId, receiveAmount, mysqlBalance, parentCouponId, childCouponId);
            logger.info("receiveMultipleCoupon:红包余额减少 amount:{},balance:{},parentCouponId:{},childCouponId:{}", receiveAmount, parentCoupon.getBalance(), parentCouponId, childCouponId);
            JSONObject result = new JSONObject();
            result.put("childCoupon", childCoupon);
            result.put("balance", mysqlBalance);
            result.put("isEmpty", isEmpty);
            return CommonResult.ok(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("receiveMultipleCoupon:接收红包出错：uId:{},parentCouponId:{},", uId, parentCouponId);
            return CommonResult.build(Constant.FUND_COUPON_EOOR, "接收红包出错，未知错误");
        } finally {
            lock.unlock();
        }
    }


    @PostMapping("/getCoupon")
    public CommonResult getCoupon(@RequestBody String couponId) {

//        Object couponObject = redisTemplate.opsForValue().get(Constant.COUPON_RECORD + couponId);
//        if (couponObject != null) {
//            return CommonResult.ok(couponObject);
//        }
        //如果缓存不存在，那就读取mysql，读取后也用不着缓存进redis ,估计当缓存失效后，红包查询频率应该很低的。
        Coupon coupon = couponMapper.selectSingleCoupon(couponId);
        return CommonResult.ok(coupon);
    }

    @PostMapping("/getSendCouponRecord")
    public CommonResult getSendCouponRecord(@RequestBody JSONObject body) {
        String uId = request.getHeader("uId");
        logger.info("查询发出的红包记录：" + uId);
        int page = 1;
        int size = 10;
        if (body.containsKey("page")) {
            page = (int) body.get("page");
        }
        if (body.containsKey("size")) {
            size = (int) body.get("size");
        }

        JSONObject result = new JSONObject();
        //获取第一页数据（第一次获取数据）的时候，才去获取总金额
        if (page == 1) {
            Double totalAmount = couponMapper.getSendTotalAmount(uId);
            result.put("totalAmount", totalAmount);
        }
       /*CouponExample example = new CouponExample();
        example.setOrderByClause("created DESC");
        CouponExample.Criteria criteria = example.createCriteria();
        criteria.andUIdEqualTo(uId);
        criteria.andTypeNotEqualTo(Constant.COUPON_TYPE_CHILD_RANDOM);
        criteria.andTypeNotEqualTo(Constant.COUPON_TYPE_CHILD_AVERAGE);*/
        PageHelper.startPage(page, size);
        List<Coupon> coupons = couponMapper.selectSendRecord(uId);
        PageInfo pageInfo = new PageInfo<>(coupons);
        result.put("pageInfo", pageInfo);
        return CommonResult.ok(result);
    }

    @PostMapping("/getReceiveCouponRecord")
    public CommonResult getReceiveCouponRecord(@RequestBody JSONObject body) {
        String uId = request.getHeader("uId");
        logger.info("查询收到的红包记录：" + uId);

        int page = 1;
        int size = 10;
        if (body.containsKey("page")) {
            page = (int) body.get("page");
        }
        if (body.containsKey("size")) {
            size = (int) body.get("size");
        }

        JSONObject result = new JSONObject();
        //获取第一页数据（第一次获取数据）的时候，才去获取总金额
        if (page == 1) {
            Double totalAmount = couponMapper.getReceiveTotalAmount(uId);
            result.put("totalAmount", totalAmount);
        }
        PageHelper.startPage(page, size);
        List<Coupon> coupons = couponMapper.selectReceivedRecord(uId);
        PageInfo pageInfo = new PageInfo<>(coupons);
        result.put("pageInfo", pageInfo);
        return CommonResult.ok(result);
    }

    /**
     * 获取一对多红包的领取记录
     *
     * @param body
     * @return
     */
    @PostMapping("/getMultipleCouponReceivedRecord")
    @SerializedField(encrypt = false)
    public CommonResult getMultipleCouponReceivedRecord(@RequestBody JSONObject body) {

        String parentCouponId = body.getString("couponId");
        int page = 1;
        int size = 10;
        if (body.containsKey("page")) {
            page = (int) body.get("page");
        }
        if (body.containsKey("size")) {
            size = (int) body.get("size");
        }

        JSONObject result = new JSONObject();
        //查询一对多红包最新信息，包括 领取数量，余额，状态等，
        if (page == 1) {
            Object coupon;
            coupon = redisTemplate.opsForValue().get(Constant.COUPON_RECORD + parentCouponId);
            if (coupon == null) {
                //缓存没有的时候，就去mysql读取，这时候肯定过期了，读取之后也用不着再缓存进redis 了，被查看的频率的应该极小了
                coupon = couponMapper.selectByPrimaryKey(parentCouponId);
            }
            result.put("parentCoupon", coupon);
        }
        PageHelper.startPage(page, size);
        List<Coupon> coupons = couponMapper.selectChildCoupon(parentCouponId);
        PageInfo pageInfo = new PageInfo<>(coupons);
        result.put("pageInfo", pageInfo);
        return CommonResult.ok(result);
    }

    @PostMapping("/getCouponStatus")
    public CommonResult getCouponStatus(@RequestBody String parentCouponId) {
        String uId = request.getHeader("uId");

//        Object couponObject = redisTemplate.opsForValue().get(Constant.COUPON_RECORD + parentCouponId);
//        if (couponObject == null) {
//            return CommonResult.build(Constant.FUND_COUPON_STATUS_EXPIRED, "红包已经过期");
//        }

        Boolean member = redisTemplate.opsForSet().isMember(Constant.COUPON_RECEIVED_RECORD + parentCouponId, uId);
        if (member) {//如果已经领取过该红包，需要去mysql 取出子红包 ，返回给客户端
            Coupon childCoupon = couponMapper.selectChildCoupon_v1(parentCouponId, uId);
            childCoupon.setParentId(parentCouponId);
            return CommonResult.ok(childCoupon);
        }
        //member是false,有可能是没有领取过，也可能是领完了
        return CommonResult.ok();
    }


    private User getUser(String uId) {
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(Constant.USER_SESSION + uId);
        if(entries.isEmpty()){
            return  userMapper.selectByPrimaryKey(uId);
        }
        return JSONObject.parseObject(JSONObject.toJSONString(entries), User.class);
    }
}
